package idv.takeshi.software.productline.bookstore.domain.model.book;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

import idv.takeshi.software.productline.bookstore.domain.model.book.InventoryStatus;

/**
 * A domain model as a Inventory for each Book.
 * @author takeshi
 *
 */
@Entity
public class Inventory {
	
	@Id
    @GeneratedValue(strategy = GenerationType.TABLE)
	private Long id;
	
	@Column(nullable = false, length = 50)
	@Enumerated(EnumType.STRING)
	private InventoryStatus status = InventoryStatus.INACTIVE;
	 
	/**
	 * AvailableQuantity = realQuantity - waitForShippinguantity.
	 * Could be minus
	 */
	@Column(nullable = false, name = "AVAILABLE_QUANTITY")
	private int availableQuantity;
	 
	/**
	 * SaftyStockQuantity as a quota for safety stock concerns while replenishing needed.
	 */
	@Column(nullable = false, name = "SAFETY_STOCK")
	private int safetyStockQuantity;
	 
	/**
	 * Quantity for replenishing.
	 */
	@Column(nullable = false, name = "REPLENISH_QUANTITY")
	private int replenishingQuantity;
	 
	/**
	 * Real quantity.
	 */
	@Column(nullable = false, name = "QUANTITY")
	private int quantity;
	
	/**
	 * Store incremental sold quantity.
	 */
	@Column(nullable = false, name = "SOLD_QUANTITY")
	private long incrementalSoldQuantity;
	 
	 
	/**
	 * @return the id
	 */
	public Long getId() {
		return id;
	}

	/**
	 * @param id the id to set
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * Test whether the safetyStockQuantity is set.
	 * @return
	 */
	public boolean hasSafetyStock() {
		return safetyStockQuantity > 0;
	}
	 
	/**
	 * Set a quantity for waiting for shipping on later future.
	 * @param waitForShippingQuantity
	 */
	public void setWaitForShippingQuantity(int waitForShippingQuantity) {
		Validate.isTrue(waitForShippingQuantity > 0, "waitForShippingQuantity:" +
				waitForShippingQuantity + " shall always greater than 0");
		if(waitForShippingQuantity > availableQuantity){
			if(availableQuantity >= 0){
				replenishingQuantity = replenishingQuantity + 
					(waitForShippingQuantity - availableQuantity);
			} else{
				replenishingQuantity = replenishingQuantity + waitForShippingQuantity;
			}
			if(this.hasSafetyStock()){
				replenishingQuantity = getReplenishQuantityBySafetyStock();
			}
		} 
		availableQuantity -= waitForShippingQuantity;
	}

	/**
	 * Test whether need to replenish.
	 * @return
	 */
	public boolean needToReplenish() {
		return replenishingQuantity > 0;
	}
	 
	/**
	 * Add real quantity.
	 * @param newQuantity
	 */
	public void addQuantity(int newQuantity) {
		Validate.isTrue(newQuantity > 0, "newQuantity:" + newQuantity + 
				" shall always greater than 0");
		if(newQuantity > replenishingQuantity){
			replenishingQuantity = 0;
		} else{
			replenishingQuantity -= newQuantity;
		}
		availableQuantity += newQuantity;
		quantity += newQuantity;
	}
	 
	/**
	 * Set quantity for shipping.
	 * @param shippingQuantity
	 */
	public void setShippingQuantity(int shippingQuantity) {
		Validate.isTrue(shippingQuantity > 0, "shippingQuantity:" + shippingQuantity +
				" shall always greater than 0");
		Validate.isTrue(shippingQuantity <= quantity, "shippingQuantity:" + shippingQuantity +
				" shall small than or equals to quantity:" + quantity);
		int tmpQuantity = quantity - shippingQuantity;
		Validate.isTrue(tmpQuantity >= availableQuantity, "quantity:" + quantity +
				" shall greater than or equals to availableQuantity:" + availableQuantity);
		quantity = tmpQuantity;
		incrementalSoldQuantity += shippingQuantity;
	}
	 
	/**
	 * Cancel the quantity which shall be set earlier in {@link #setWaitForShippingQuantity(int)}.
	 * @param waitForShippingQuantity
	 */
	public void cancelWaitForShippingQuantity(int waitForShippingQuantity) {
		Validate.isTrue(waitForShippingQuantity > 0, "waitForShippingQuantity:" + 
				waitForShippingQuantity + " shall always greater than 0");
		
		int tmpAvailableQuantity = availableQuantity > 0? availableQuantity: 0;
		Validate.isTrue(waitForShippingQuantity <= (quantity - tmpAvailableQuantity), 
				"waitForShippingQuantity:" + waitForShippingQuantity + 
				" shall small than or equals to " + (quantity - tmpAvailableQuantity));
		
		if(waitForShippingQuantity > replenishingQuantity){
			replenishingQuantity = 0;
		} else{
			replenishingQuantity -= waitForShippingQuantity;
			if(this.hasSafetyStock()){
				replenishingQuantity = getReplenishQuantityBySafetyStock();
			}
		}
		availableQuantity += waitForShippingQuantity;
	}

	/**
	 * @return the availableQuantity
	 */
	public int getAvailableQuantity() {
		return availableQuantity;
	}

	/**
	 * @return the safetyStockQuantity
	 */
	public int getSafetyStockQuantity() {
		return safetyStockQuantity;
	}

	/**
	 * @return the replenishingQuantity
	 */
	public int getReplenishingQuantity() {
		return replenishingQuantity;
	}

	/**
	 * @return the status
	 */
	public InventoryStatus getStatus() {
		return status;
	}

	/**
	 * @param status the status to set
	 */
	public void setStatus(InventoryStatus status) {
		this.status = status;
	}

	/**
	 * @return the quantity
	 */
	public int getQuantity() {
		return quantity;
	}

	/**
	 * @param safetyStockQuantity the safetyStockQuantity to set
	 */
	public void setSafetyStockQuantity(int safetyStockQuantity) {
		Validate.isTrue(safetyStockQuantity > 0, "safetyStockQuantity:" + safetyStockQuantity +
				" shall always greater than 0");
		this.safetyStockQuantity = safetyStockQuantity;
	}

	/**
	 * @return the incrementalSoldQuantity
	 */
	public long getIncrementalSoldQuantity() {
		return incrementalSoldQuantity;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == null) { return false; }
		   if (obj == this) { return true; }
		   if (obj.getClass() != getClass()) {
		     return false;
		   }
		   Inventory rhs = (Inventory) obj;
		   return new EqualsBuilder()
		       .append(this.id, rhs.id)
			   .append(this.status, rhs.status)
			   .append(this.availableQuantity, rhs.availableQuantity)
			   .append(this.safetyStockQuantity, rhs.safetyStockQuantity)
			   .append(this.replenishingQuantity, rhs.replenishingQuantity)
			   .append(this.quantity, rhs.quantity)
			   .append(this.incrementalSoldQuantity, rhs.incrementalSoldQuantity)
		       .isEquals();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return new HashCodeBuilder(5, 7).
		   append(this.id).
	       append(this.status).
	       append(this.availableQuantity).
		   append(this.safetyStockQuantity).
		   append(this.replenishingQuantity).
		   append(this.quantity).
		   append(this.incrementalSoldQuantity).
	       toHashCode();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
	}

	/**
	 * Calculate the rreplenishingQuantity by safetyStock.
	 * @return
	 */
	private int getReplenishQuantityBySafetyStock() {
		int tmpQuantity = 0;
		while(tmpQuantity < replenishingQuantity){
			tmpQuantity += safetyStockQuantity;
		}
		return tmpQuantity;
	}
	 
}
 
